﻿/**
 * @file config.h
 * @author Yunfeng.Bo (Yunfeng.Bo@qq.com)
 * @brief 配置管理，使用 TOML 格式，并用 tomlplusplus 库进行读写
 *      初始化：CONFIG->init("config filename")
 *      退出：  CONFIG->exit()
 *      读取配置：CONF["aa"]["bb"].value_or(default_value)
 *      修改（新增）配置：CONFIG->set_value_at_path(path, value);
 *              // path 在修改时，可以是多级；新增时，只能在已有的结点下增加一级
 *      保存：CONFIG->save(config_filename);
 * @version 0.1
 * @date 2022-01-20
 *
 * @copyright Copyright (c) 2022
 *
 */

#pragma once

#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>

#include "logger.h"
#include "toml.hpp"

// 配置管理
#define CONFIG config::get_instance()
#define CONF config::get_instance()->get()

class config {
public:
    config() {}
    virtual ~config() {}

    static config* get_instance() {
        static config s_instance;
        return &s_instance;
    }

    int init_from_file(const std::string& config_filename) {
        // 加载配置文件
        try {
            m_conf = toml::parse_file(config_filename);
        } catch (toml::v3::ex::parse_error& er) {
            LOG_ERROR << "Parse config file failed: " << config_filename << ", " << er.what();
            return -1;
        }

        return 0;
    }

    int init_from_string(const std::string& doc) {
        // 加载配置文件
        try {
            m_conf = toml::parse(doc);
        } catch (toml::v3::ex::parse_error& er) {
            LOG_ERROR << "Parse config failed, content: " << doc << ", " << er.what();
            return -1;
        }

        return 0;
    }

    void exit() {}

    const toml::v3::table& get() { return m_conf; }

    int save(const std::string& config_filename) {
        std::ofstream fout(config_filename);
        fout << m_conf;
        fout.close();
        return 0;
    }

    template<typename T>
    T value_at_path(const std::string& path, const T& default_value) {
        if (path.empty())
            return default_value;

        std::vector<std::string> paths;
        boost::split(paths, path, boost::is_any_of("/"), boost::token_compress_on);

        // 允许路径中，以 / 开头
        if (paths.at(0).empty())
            paths.erase(paths.begin());

        std::string root = paths.at(0);
        if (paths.size() <= 1)
            return m_conf[root].value_or(default_value);

        paths.erase(paths.begin());
        return value_at_path<T>(m_conf[root], paths, default_value);
    }

    template<typename T>
    int set_value_at_path(const std::string& path, const T& value) {
        if (path.empty())
            return -1;

        std::vector<std::string> paths;
        boost::split(paths, path, boost::is_any_of("/"), boost::token_compress_on);

        // 允许路径中，以 / 开头
        if (paths.at(0).empty())
            paths.erase(paths.begin());

        std::string root = paths.at(0);
        if (paths.size() <= 1) {
            m_conf.insert_or_assign(root, value);
            return 0;
        }

        paths.erase(paths.begin());
        return set_value_at_path<T>(m_conf[root], paths, value);
    }

private:
    template<typename T>
    T value_at_path(const toml::v3::node_view<toml::v3::node>& node_view,
                    std::vector<std::string> paths, const T& default_value) {
        std::string root = paths.at(0);
        if (paths.size() <= 1) {
            return node_view[root].value_or(default_value);
        }

        paths.erase(paths.begin());
        return value_at_path<T>(node_view[root], paths, default_value);
    }

    template<typename T>
    int set_value_at_path(const toml::v3::node_view<toml::v3::node> node_view,
                          std::vector<std::string> paths, const T& value) {
        std::string root = paths.at(0);
        if (paths.size() <= 1) {
            if (!node_view.node()->is_table())
                return -1;

            node_view.node()->as_table()->insert_or_assign(root, value);
            return 0;
        }

        paths.erase(paths.begin());
        return set_value_at_path<T>(node_view[root], paths, value);
    }

private:
    toml::v3::table m_conf;
};